﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using Spencen.Mobile.UI.Animations;
using Spencen.Mobile.UI.Easings;

namespace Spencen.Mobile.UI.Behaviors
{
    public class DragBehavior : Behavior
    {
        private DrawingElement _selectedElement;
        private Point _dragOffset;
        private bool _isMouseDown;
        private Point _lastMove;
        private Point _secondLastMove;
        private long _lastMoveTick;
        private long _secondLastMoveTick;

        public DragBehavior()
        {
            DragAxis = ScrollBars.Both;
            AllowSlide = true;
            ConstrainToParent = true;
        }

        public ScrollBars DragAxis { get; set; }
        public bool AllowSlide { get; set; }
        public bool ConstrainToParent { get; set; }

        public override void OnAttached( DrawingElement target )
        {
            base.OnAttached( target );
            target.MouseDown += target_MouseDown;
            target.MouseUp += target_MouseUp;
            target.MouseMove += target_MouseMove;
        }

        public override void OnDettached( DrawingElement target )
        {
            base.OnDettached( target );
            target.MouseDown -= target_MouseDown;
            target.MouseUp -= target_MouseUp;
            target.MouseMove -= target_MouseMove;
        }

        private void target_MouseDown( object sender, MouseEventArgs e )
        {
            if ( e.Button == MouseButtons.Left )
            {
                _selectedElement = sender as DrawingElement;
                if ( _selectedElement != null )
                    _dragOffset = new Point( e.X - _selectedElement.Position.X, e.Y - _selectedElement.Position.Y );

                _isMouseDown = true;
            }
        }

        private void target_MouseMove( object sender, MouseEventArgs e )
        {
            if ( _selectedElement == sender && _isMouseDown )
            {
                // Invalidate the current position
                _selectedElement.Invalidate();
                
                var mouseOffset = new Point( e.X - _dragOffset.X - _selectedElement.Position.X, e.Y - _dragOffset.Y - _selectedElement.Position.Y );
                _selectedElement.Position = new Point(
                    _selectedElement.Position.X + ( (DragAxis == ScrollBars.Both || DragAxis == ScrollBars.Horizontal ) ? mouseOffset.X : 0 ), 
                    _selectedElement.Position.Y + ( (DragAxis == ScrollBars.Both || DragAxis == ScrollBars.Vertical ) ? mouseOffset.Y : 0 ) );

                if ( ConstrainToParent )
                    _selectedElement.Position = ConstrainPoints( _selectedElement, _selectedElement.Position );

                // Invalidate the new position
                _selectedElement.Invalidate();

                // Record last two moves in order to calculate velocity and angle
                var moveTick = Environment.TickCount;
                var move = new Point( e.X, e.Y );
                if ( moveTick != _lastMoveTick && move != _lastMove )
                {
                    _secondLastMove = _lastMove;
                    _secondLastMoveTick = _lastMoveTick;

                    _lastMoveTick = moveTick;
                    _lastMove = move;
                }

                //System.Diagnostics.Debug.WriteLine( string.Format( "Move {0:000000000} ({1},{2})", Environment.TickCount, e.X, e.Y ) ); 
            }
        }

        private Point ConstrainPoints( object selected, Point point )
        {
            var selectedElement = (DrawingElement)selected;
            if ( selectedElement.Parent == null ) return point;

            var constrainedPoint = point;
            if ( constrainedPoint.X < 0 ) constrainedPoint.X = 0;
            if ( constrainedPoint.Y < 0 ) constrainedPoint.Y = 0;
            if ( constrainedPoint.X > selectedElement.Parent.Size.Width - selectedElement.Size.Width ) 
                constrainedPoint.X = selectedElement.Parent.Size.Width - selectedElement.Size.Width;
            if ( constrainedPoint.Y > selectedElement.Parent.Size.Height - selectedElement.Size.Height ) 
                constrainedPoint.Y = selectedElement.Parent.Size.Height - selectedElement.Size.Height;

            return constrainedPoint;
        }

        private void target_MouseUp( object sender, MouseEventArgs e )
        {
            if ( _selectedElement != null && _isMouseDown )
            {
                //System.Diagnostics.Debug.WriteLine( string.Format( "Up   {0:000000000} ({1},{2})", Environment.TickCount, e.X, e.Y ) );

                _lastMoveTick = Environment.TickCount;
                if ( AllowSlide && _lastMoveTick - _secondLastMoveTick < 1000 && _lastMoveTick > 0 && _secondLastMoveTick > 0 )
                {
                    float duration = _lastMoveTick - _secondLastMoveTick;
                    float distance = DrawingSupport.DistanceBetweenPoints( _secondLastMove, _lastMove );
                    if ( duration > 0 && distance > 0 )
                    {
                        float velocity = distance / duration;
                        float angle = DrawingSupport.AngleBetweenPoints( _secondLastMove, _lastMove );

                        var slideAnimation = new PointAnimation( sender, "Position", new TimeSpan( 0, 0, 0, 0, (int) (1000 ) ) );
                        var x =  _selectedElement.Position.X +  ( (DragAxis == ScrollBars.Both || DragAxis == ScrollBars.Horizontal ) ? ( Math.Cos( DrawingSupport.DegToRad( angle ) ) * 1000 * velocity * velocity ) : 0 );
                        var y =  _selectedElement.Position.Y +  ( (DragAxis == ScrollBars.Both || DragAxis == ScrollBars.Vertical ) ? ( Math.Sin( DrawingSupport.DegToRad( angle ) ) * 1000 * velocity * velocity ) : 0 ) ;
                        slideAnimation.FinalValue = new Point( (int) x, (int) y);

                        slideAnimation.EasingFunction = new CubicEasing();
                        slideAnimation.Constraint = new Func<object,Point,Point>( ConstrainPoints );

                        _selectedElement.Host.AnimationManager.AddAnimation( slideAnimation, true );
                    }
                }
            }

            _selectedElement = null;
            _isMouseDown = false;
            _lastMoveTick = 0;
            _lastMove = Point.Empty;
        }
    }
}
